package com.xiaoxin.xinlikesystem.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaoxin.xinlikesystem.constant.ThumbConstant;
import com.xiaoxin.xinlikesystem.exception.ErrorCode;
import com.xiaoxin.xinlikesystem.exception.ThrowUtils;
import com.xiaoxin.xinlikesystem.model.dto.thumb.DoThumbRequest;
import com.xiaoxin.xinlikesystem.model.entity.Blog;
import com.xiaoxin.xinlikesystem.model.entity.Thumb;
import com.xiaoxin.xinlikesystem.model.entity.User;
import com.xiaoxin.xinlikesystem.service.BlogService;
import com.xiaoxin.xinlikesystem.service.ThumbService;
import com.xiaoxin.xinlikesystem.mapper.ThumbMapper;
import com.xiaoxin.xinlikesystem.service.UserService;
import com.xiaoxin.xinlikesystem.utils.RedisKeyUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.redisson.api.RLock;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.time.LocalDate;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

import static com.xiaoxin.xinlikesystem.constant.ThumbConstant.*;

/**
 * @author Xiaoxin
 * @description 针对表【thumb(点赞表)】的数据库操作Service实现
 * @createDate 2025-05-10 14:00:24
 */
@Service
public class ThumbServiceImpl extends ServiceImpl<ThumbMapper, Thumb>
        implements ThumbService {

    @Resource
    private UserService userService;

    @Resource
    private BlogService blogService;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private TransactionTemplate transactionTemplate;


    /**
     * 点赞
     *
     * @param doThumbRequest 点赞请求
     * @param request        请求
     * @return {@link Boolean }
     */
    @Override
    public Boolean doThumb(DoThumbRequest doThumbRequest, HttpServletRequest request) {
        // 1. 获取当前登录用户对象、博客Id
        User loginUser = userService.getLoginUser(request);
        Long blogId = doThumbRequest.getBlogId();

        // 2. 加分布式锁+事务（单机客户端，容易导致内存溢出）
        String lockKey = RedisKeyUtil.getDoThumbLockKey(blogId, loginUser.getId());
        RLock lock = redissonClient.getLock(lockKey);
        boolean success = false;
        try {
            if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
                // 编程式事务（Spring）
                success = Boolean.TRUE.equals(transactionTemplate.execute(status -> {
                    // 2.1 是否已经点赞
                    Long thumbId = this.hasThumb(blogId, loginUser.getId());
                    ThrowUtils.throwIf(thumbId != null, ErrorCode.OPERATION_ERROR, "用户已点赞");

                    // 2.2 更新点赞数（数据库原子操作）
                    boolean update = blogService.lambdaUpdate()
                            .eq(Blog::getId, blogId)
                            .setSql("thumbCount = thumbCount + 1")
                            .update();

                    // 2.3 保存点赞记录
                    Thumb thumb = new Thumb();
                    thumb.setBlogId(blogId);
                    thumb.setUserId(loginUser.getId());
                    boolean saved = this.save(thumb);

                    // 2.4 删除缓存保证缓存一致性
                    if (update && saved) {
                        deleteCache(loginUser, blogId);
                    }

                    return update && saved;
                }));
            }
        } catch (InterruptedException e) {
            log.error("获取分布式锁失败", e);
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();  // 必须手动释放
            }
        }
        return success;
    }


    /**
     * 取消点赞
     *
     * @param doThumbRequest 取消点赞请求
     * @param request        请求
     * @return {@link Boolean }
     */
    @Override
    public Boolean undoThumb(DoThumbRequest doThumbRequest, HttpServletRequest request) {
        // 1. 获取当前登录用户对象、博客Id
        User loginUser = userService.getLoginUser(request);
        Long blogId = doThumbRequest.getBlogId();

        // 2. 加分布式锁+事务
        String lockKey = RedisKeyUtil.getUndoThumbLockKey(blogId, loginUser.getId());
        RLock lock = redissonClient.getLock(lockKey);
        boolean success = false;
        try {
            if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
                // 编程式事务（Spring）
                success = Boolean.TRUE.equals(transactionTemplate.execute(status -> {
                    // 2.1 是否已经点赞
                    Long thumbId = hasThumb(blogId, loginUser.getId());
                    ThrowUtils.throwIf(thumbId == null, ErrorCode.OPERATION_ERROR, "用户未点赞");

                    // 2.2 更新点赞数（数据库原子操作）
                    boolean update = blogService.lambdaUpdate()
                            .eq(Blog::getId, blogId)
                            .setSql("thumbCount = thumbCount - 1")
                            .update();

                    // 2.3 删除点赞记录与缓存,删除缓存保证缓存一致性
                    boolean result = update && this.removeById(thumbId);
                    if (result) {
                        deleteCache(loginUser, blogId);
                    }
                    return result;
                }));
            }
        } catch (InterruptedException e) {
            log.error("获取分布式锁失败", e);
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();  // 必须手动释放
            }
        }
        return success;
    }


    /**
     * 是否点赞
     *
     * @param blogId 博客id
     * @param userId 用户id
     * @return 返回点赞记录Id
     */
    @Override
    public Long hasThumb(Long blogId, Long userId) {
        // 1. 查询查 Redis 缓存(RMapCache是Redis增强版缓存，可设置过期时间)
        String redisKey = RedisKeyUtil.getUserThumbKey(userId);
        RMapCache<String, Long> userThumbMap = redissonClient.getMapCache(redisKey);
        Long thumbId = userThumbMap.get(blogId.toString());

        // 2. 缓存命中
        if (thumbId != null) {
            // 若值为 -1L → 表示未点赞，直接返回 null；
            if (EMPTY_THUMB_PLACEHOLDER.equals(thumbId)) {
                return null;
            }
            return thumbId;
        }

        // 3. 缓存未命中，回源数据库
        Thumb thumb = this.lambdaQuery()
                .eq(Thumb::getBlogId, blogId)
                .eq(Thumb::getUserId, userId)
                .one();
        // 3.1 查询博客是否为热点数据
        Blog blog = blogService.getById(blogId);
        boolean isHot = false;
        if (blog != null) {
            // 点赞数量是否超过阈值
            boolean likeHot = blog.getThumbCount() != null && blog.getThumbCount() > ThumbConstant.HOT_BLOG_LIKE_THRESHOLD;
            // 博客创建时间是否超过三十天
            long days = ChronoUnit.DAYS.between(
                    blog.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(),
                    LocalDate.now()
            );
            boolean recent = days <= 30;
            // 判断是否是热门博客
            isHot = likeHot || recent;
        }
        // 根据是否为热点数据设置缓存时间。热点数据则设置30天有效期，非热点数据5分钟防止缓存穿透
        int cacheTime = isHot ? THUMB_HOT_TIME : THUMB_TIME;
        // 3.2 未点赞
        if (thumb == null) {
            userThumbMap.put(blogId.toString(), EMPTY_THUMB_PLACEHOLDER, cacheTime, TimeUnit.MINUTES);
            return null;
        }
        // 3.3 已点赞
        // 热点博客写入缓存（否则只返回）
        if (isHot) {
            userThumbMap.put(blogId.toString(), thumb.getId(), cacheTime, TimeUnit.MINUTES);
        }
        return thumb.getId();
    }


    /**
     * 删除是否点赞缓存
     *
     * @param loginUser 登录用户
     * @param blogId    博客ID
     */
    private void deleteCache(User loginUser, Long blogId) {
        String redisKey = RedisKeyUtil.getUserThumbKey(loginUser.getId());
        RMapCache<String, Long> userThumbMap = redissonClient.getMapCache(redisKey);
        userThumbMap.remove(blogId.toString());
    }
}




